/* eslint max-lines: "off" */ //临时豁免 关闭ESLint的最大代码行数max-lines规则
const moment = require('moment');
const CHILDREN_LIST = ['table', 'dynamic'];
const DIC_PROPS = {
  rowKey: 'id',
  rowParentKey: 'parentId',
  nodeKey: 'id',
  label: 'label',
  value: 'value',
  desc: 'desc',
  groups: 'groups',
  title: 'title',
  leaf: 'leaf',
  children: 'children',
  hasChildren: 'hasChildren',
  labelText: '名称',
  disabled: 'disabled'
};
const DIC_SPLIT = ',';
const DIC_SHOW_SPLIT = ' | ';
const DATE_LIST = ['dates', 'date', 'datetime', 'datetimerange', 'daterange', 'time', 'timerange', 'week', 'month', 'monthrange', 'year'];
const MULTIPLE_LIST = ['cascader', 'tree', 'select'];
const ARRAY_LIST = ['img', 'array', 'url'];
const ARRAY_VALUE_LIST = ARRAY_LIST.concat(['upload', 'dynamic', 'map', 'checkbox', 'cascader', 'timerange', 'monthrange', 'daterange', 'datetimerange', 'dates']);

function buildHeader(revealList) {
  let excelHeader = [];
  // 构建生成excel表头需要的数据结构
  getHeader(revealList, excelHeader, 0, 0);
  // 多行表头长短不一，短的向长的看齐，不够的补上行合并占位符
  let max = Math.max(...excelHeader.map((a) => a.length));
  excelHeader.filter((e) => e.length < max).forEach((e) => pushRowSpanPlaceHolder(e, max - e.length));
  return excelHeader;
}

/**
 * 生成头部
 * @param headers 展示的头部
 * @param excelHeader excel头部
 * @param deep 深度
 * @param perOffset 前置偏移量
 * @returns {number}  后置偏移量
 */
function getHeader(headers, excelHeader, deep, perOffset) {
  let offset = 0;
  let cur = excelHeader[deep];
  if (!cur) {
    cur = excelHeader[deep] = [];
  }
  // 填充行合并占位符
  pushRowSpanPlaceHolder(cur, perOffset - cur.length);
  for (let i = 0; i < headers.length; i++) {
    let head = headers[i];
    cur.push(head.label);
    if (head.hasOwnProperty('children') && Array.isArray(head.children) && head.children.length > 0) {
      let childOffset = getHeader(head.children, excelHeader, deep + 1, cur.length - 1);
      // 填充列合并占位符
      pushColSpanPlaceHolder(cur, childOffset - 1);
      offset += childOffset;
    } else {
      offset++;
    }
  }
  return offset;
}
// 填充列合并占位符
function pushColSpanPlaceHolder(arr, count) {
  for (let i = 0; i < count; i++) {
    arr.push('!$COL_SPAN_PLACEHOLDER');
  }
}
/**
 * 填充行合并占位符
 * */
function pushRowSpanPlaceHolder(arr, count) {
  for (let i = 0; i < count; i++) {
    arr.push('!$ROW_SPAN_PLACEHOLDER');
  }
}
const isJson = (str) => {
  if (Array.isArray(str)) {
    if (str[0] instanceof Object) {
      return true;
    } else {
      return false;
    }
  } else if (str instanceof Object) {
    return true;
  }
  return false;
};

function downFile(url, saveName) {
  if (typeof url == 'object' && url instanceof Blob) {
    url = URL.createObjectURL(url); // 创建blob地址
  }
  var aLink = document.createElement('a');
  aLink.href = url;
  aLink.download = saveName || ''; // HTML5新增的属性，指定保存文件名，可以不要后缀，注意，file:///模式下不会生效
  var event;
  if (window.MouseEvent) {
    event = new MouseEvent('click');
  } else {
    event = document.createEvent('MouseEvents');
    event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
  }
  aLink.dispatchEvent(event);
}
function aoa_to_sheet(data, headerRows) {
  const ws = {};
  const range = { s: { c: 10000000, r: 10000000 }, e: { c: 0, r: 0 } };
  for (let R = 0; R !== data.length; ++R) {
    for (let C = 0; C !== data[R].length; ++C) {
      if (range.s.r > R) {
        range.s.r = R;
      }
      if (range.s.c > C) {
        range.s.c = C;
      }
      if (range.e.r < R) {
        range.e.r = R;
      }
      if (range.e.c < C) {
        range.e.c = C;
      }
      /// 这里生成cell的时候，使用上面定义的默认样式
      const cell = {
        v: vaildData(data[R][C], ''),
        s: {
          font: { name: '宋体', sz: 11, color: { auto: 1, rgb: '000000' }, bold: true },
          alignment: {
            /// 自动换行
            wrapText: 1,
            // 居中
            horizontal: 'center',
            vertical: 'center',
            indent: 0
          }
        }
      };
      // 头部列表加边框
      if (R < headerRows) {
        cell.s.border = {
          top: { style: 'thin', color: { rgb: 'EBEEF5' } },
          left: { style: 'thin', color: { rgb: 'EBEEF5' } },
          bottom: { style: 'thin', color: { rgb: 'EBEEF5' } },
          right: { style: 'thin', color: { rgb: 'EBEEF5' } }
        };
        cell.s.fill = {
          patternType: 'solid',
          fgColor: { theme: 3, tint: 0.3999755851924192, rgb: 'F5F7FA' },
          bgColor: { theme: 7, tint: 0.3999755851924192, rgb: 'F5F7FA' }
        };
      }
      const cell_ref = XLSX.utils.encode_cell({ c: C, r: R });
      if (typeof cell.v === 'number') {
        cell.t = 'n';
      } else if (typeof cell.v === 'boolean') {
        cell.t = 'b';
      } else {
        cell.t = 's';
      }
      ws[cell_ref] = cell;
    }
  }
  if (range.s.c < 10000000) {
    ws['!ref'] = XLSX.utils.encode_range(range);
  }
  return ws;
}
function s2ab(s) {
  let buf = new ArrayBuffer(s.length);
  let view = new Uint8Array(buf);
  for (let i = 0; i !== s.length; ++i) {
    view[i] = s.charCodeAt(i) & 0xff;
  }
  return buf;
}
// 导出 excel
export function excel(params) {
  if (!window.XLSX) {
    console.warn('xlsx 需要插件');
    return;
  }
  return new Promise((resolve, reject) => {
    // 合并参数
    const _params = {
      prop: []
    };

    // 从参数中派生数据
    _params.header = buildHeader(params.columns);
    _params.title = params.title || moment().format('YYYY-MM-DD HH:mm:ss');
    const callback = (list) => {
      list.forEach((ele) => {
        if (ele.children && ele.children instanceof Array) {
          callback(ele.children);
        } else {
          _params.prop.push(ele.prop);
        }
      });
    };
    callback(params.columns);
    _params.data = params.data.map((row) =>
      _params.prop.map((prop) => {
        let data = row[prop];
        if (isJson(data)) data = JSON.stringify(data);
        return data;
      })
    );
    let headerRows = _params.header.length;
    _params.header.push(..._params.data, []);
    // 计算合并
    let merges = doMerges(_params.header);
    // 生成sheet
    let ws = aoa_to_sheet(_params.header, headerRows);
    // 单元格合并
    ws['!merges'] = merges;
    // 头部冻结
    ws['!freeze'] = {
      xSplit: '1',
      ySplit: '' + headerRows,
      topLeftCell: 'B' + (headerRows + 1),
      activePane: 'bottomRight',
      state: 'frozen'
    };
    // 列宽
    ws['!cols'] = [{ wpx: 165 }];
    let workbook = {
      SheetNames: ['Sheet1'],
      Sheets: {}
    };
    workbook.Sheets.Sheet1 = ws;
    // excel样式
    let wopts = {
      bookType: 'xlsx',
      bookSST: false,
      type: 'binary',
      cellStyles: true
    };
    let wbout = XLSX.write(workbook, wopts);
    let blob = new Blob([s2ab(wbout)], { type: 'application/octet-stream' });
    downFile(blob, _params.title + '.xlsx');
    resolve();
  });
}

function doMerges(arr) {
  // 要么横向合并 要么纵向合并
  let deep = arr.length;
  let merges = [];
  for (let y = 0; y < deep; y++) {
    // 先处理横向合并
    let row = arr[y];
    let colSpan = 0;
    for (let x = 0; x < row.length; x++) {
      if (row[x] === '!$COL_SPAN_PLACEHOLDER') {
        row[x] = undefined;
        if (x + 1 === row.length) {
          merges.push({ s: { r: y, c: x - colSpan - 1 }, e: { r: y, c: x } });
        }
        colSpan++;
      } else if (colSpan > 0 && x > colSpan) {
        merges.push({ s: { r: y, c: x - colSpan - 1 }, e: { r: y, c: x - 1 } });
        colSpan = 0;
      } else {
        colSpan = 0;
      }
    }
  }
  // 再处理纵向合并
  let colLength = arr[0].length;
  for (let x = 0; x < colLength; x++) {
    let rowSpan = 0;
    for (let y = 0; y < deep; y++) {
      if (arr[y][x] === '!$ROW_SPAN_PLACEHOLDER') {
        arr[y][x] = undefined;
        if (y + 1 === deep) {
          merges.push({ s: { r: y - rowSpan, c: x }, e: { r: y, c: x } });
        }
        rowSpan++;
      } else if (rowSpan > 0 && y > rowSpan) {
        merges.push({ s: { r: y - rowSpan - 1, c: x }, e: { r: y - 1, c: x } });
        rowSpan = 0;
      } else {
        rowSpan = 0;
      }
    }
  }
  return merges;
}
/**
 * 计算级联属性
 */
export const calcCascader = (list = []) => {
  list.forEach((ele) => {
    let cascader = ele.cascader;
    if (!validatenull(cascader)) {
      let parentProp = ele.prop;
      cascader.forEach((citem) => {
        let column = findObject(list, citem);
        if (column) column.parentProp = parentProp;
      });
    }
  });
  return list;
};

export function loadCascaderDic(columnOption, safe) {
  return new Promise((resolve) => {
    let list = [];
    let result = {};
    let columnList = columnOption.filter((ele) => ele.parentProp);
    safe.data.forEach((ele, index) => {
      if (!safe.cascaderDIC[index]) safe.$set(safe.cascaderDIC, index, {});
      columnList.forEach((column) => {
        if (column.hide !== true && column.dicFlag !== false) {
          list.push(
            new Promise((resolve) => {
              if (ele[column.parentProp]) {
                sendDic({
                  url: column.dicUrl,
                  props: column.props,
                  method: column.dicMethod,
                  headers: column.dicHeaders,
                  formatter: column.dicFormatter,
                  query: column.dicQuery,
                  dataType: column.dataType,
                  form: ele,
                  value: ele[column.parentProp]
                }).then((res) => {
                  let obj = {
                    prop: column.prop,
                    data: res,
                    index: index
                  };
                  safe.$set(safe.cascaderDIC[index], obj.prop, obj.data);
                  resolve(obj);
                });
              } else {
                let obj = {
                  prop: column.prop,
                  data: [],
                  index: index
                };
                safe.$set(safe.cascaderDIC[index], obj.prop, obj.data);
                resolve(obj);
              }
            })
          );
        }
      });
    });
    Promise.all(list).then((data) => {
      data.forEach((ele) => {
        if (!result[ele.index]) result[ele.index] = {};
        result[ele.index][ele.prop] = ele.data;
      });
      resolve(result);
    });
  });
}

export function loadDic(option, safe) {
  return new Promise((resolve) => {
    let list = [],
      result = {};
    let notList = [],
      nameList = [],
      column = option.column || [];
    column.forEach((ele) => {
      let url = ele.dicUrl;
      let prop = ele.prop;
      let parentProp = ele.parentProp;
      notList = notList.concat(ele.cascader || []);
      let flag = ele.dicFlag === false || ele.lazy === true || notList.includes(prop);
      if (url && !parentProp && !flag) {
        list.push(
          new Promise((resolve) => {
            sendDic({
              url: url,
              name: prop,
              method: ele.dicMethod,
              headers: ele.dicHeaders,
              formatter: ele.dicFormatter,
              props: ele.props,
              dataType: ele.dataType,
              query: ele.dicQuery
            }).then((res) => {
              safe.$set(safe.DIC, prop, res);
              resolve(res);
            });
          })
        );
      }
    });
    Promise.all(list).then((res) => {
      nameList.forEach((ele, index) => {
        result[ele] = res[index];
      });
      resolve(result);
    });
  });
}

export function loadLocalDic(option, safe) {
  let columnData = {};
  let optionData = option.dicData || {};
  option.column.forEach((ele) => {
    if (ele.dicData) columnData[ele.prop] = getDataType(ele.dicData, ele.props, ele.dataType);
  });
  let result = { ...optionData, ...columnData };
  Object.keys(result).forEach((ele) => {
    safe.$set(safe.DIC, ele, result[ele]);
  });
  return result;
}

function sendDic(params) {
  let { url, query, method, props, formatter, headers, value, column = {}, form = {}, dataType } = params;
  url = column.dicUrl || url;
  method = (column.dicMethod || method || 'get').toLowerCase();
  headers = column.dicHeaders || headers || {};
  query = column.dicQuery || query || {};
  formatter = column.dicFormatter || formatter;
  props = column.props || props || {};
  let list = url.match(/[^\{\}]+(?=\})/g) || [];
  list.forEach((ele) => {
    url = url.replace(`{{${ele}}}`, ele === key ? value : form[ele]);
  });

  const getKey = (data) => {
    let result = {};
    Object.keys(data).forEach((ele) => {
      let eleKey = data[ele];
      if (typeof eleKey == 'string' && eleKey.match(/\{{|}}/g)) {
        let prop = eleKey.replace(/\{{|}}/g, '');
        result[ele] = prop == key ? value : form[prop];
      } else {
        result[ele] = eleKey;
      }
    });
    return result;
  };

  return new Promise((resolve) => {
    if (!url) resolve([]);
    const callback = (res) => {
      let list = [];
      res = res.data || {};
      if (typeof formatter === 'function') {
        list = formatter(res, form);
      } else {
        list = getResData(res, props, dataType);
      }
      resolve(list);
    };
    const getData = () => {
      let data = getKey(query);
      if (method == 'get') return { params: data };
      return { data };
    };
    window
      .axios(
        Object.assign(
          {
            url,
            method,
            headers: getKey(headers)
          },
          getData()
        )
      )
      .then(function (res) {
        callback(res);
      })
      .catch(() => [resolve([])]);
  });
}
/**
 * 判断是否为空
 */
export function validatenull(val) {
  if (val instanceof Date || typeof val === 'boolean' || typeof val === 'number') return false;
  if (val instanceof Array) {
    if (val.length === 0) return true;
  } else if (val instanceof Object) {
    for (var o in val) {
      return false;
    }
    return true;
  } else {
    if (val === 'null' || val == null || val === 'undefined' || val === undefined || val === '') {
      return true;
    }
    return false;
  }
  return false;
}

export function findObject(list = [], value, prop = 'prop') {
  let result;
  result = findNode(list, { value: prop }, value);
  if (!result) {
    list.forEach((ele) => {
      if (ele.column) {
        if (!result) result = findNode(ele.column, { value: prop }, value);
      } else if (ele.children && CHILDREN_LIST.includes(ele.type)) {
        if (!result) result = findNode(ele.column.children, { value: prop }, value);
      }
    });
  }
  return result;
}

export const findNode = (list = [], props = {}, value) => {
  let valueKey = props.value || DIC_PROPS.value;
  let childrenKey = props.children || DIC_PROPS.children;
  let node;
  list.forEach((ele) => {
    if (ele[valueKey] == value) node = ele;
    else if (ele[childrenKey]) node = findNode(ele[childrenKey], props, value);
  });
  return node;
};

export const detail = (row = {}, column = {}, option = {}, dic = []) => {
  let result = row[column.prop];
  let type = column.type;
  let separator = column.separator;
  // 深结构绑定处理
  if (column.bind) result = getAsVal(row, column.bind);
  result = result;
  if (!validatenull(result)) {
    let selectFlag = MULTIPLE_LIST.includes(column.type) && column.multiple;
    let arrayFlag = ARRAY_VALUE_LIST.includes(column.type);
    if ((selectFlag || arrayFlag) && !Array.isArray(result) && !column.dataType) column.dataType = 'string';
    if (column.dataType) {
      if (selectFlag || arrayFlag) {
        if (!Array.isArray(result)) result = result.split(separator || DIC_SPLIT);
        result.forEach((ele) => {
          ele = detailDataType(ele, column.dataType);
        });
      } else {
        result = detailDataType(result, column.dataType);
      }
    }
    if ('password' === type) {
      result = getPasswordChar(result, '*');
    } else if (DATE_LIST.includes(type) && column.format) {
      const format = column.format.replace('dd', 'DD').replace('yyyy', 'YYYY');
      let formatValue = moment().format('YYYY-MM-DD');
      if (type.indexOf('range') !== -1) {
        let date1 = result[0] || '',
          date2 = result[1] || '';
        if (type === 'timerange' && date1.length <= 8 && date2.length < 8) {
          date1 = `${formatValue} ${date1}`;
          date2 = `${formatValue} ${date2}`;
        }
        result = [moment(date1).format(format), moment(date2).format(format)].join(column.separator || '~');
      } else {
        if (type === 'time' && result.length <= 8) {
          result = `${formatValue} ${result}`;
        }
        result = moment(result).format(format);
      }
    }
    // 字典处理
    result = getDicValue(dic, result, column.props || option.props);
  }
  // 自定义格式化
  if (typeof column.formatter === 'function') {
    let domReg = /(?<=(<span[^>]*?>)).*?(?=(<\/span>))/g;
    if (column.formatter(row, row[column.prop])) {
      if (typeof column.formatter(row, row[column.prop], result, column) === 'string' && column.formatter(row, row[column.prop], result, column).match(domReg)) {
        result = column.formatter(row, row[column.prop], result, column).match(domReg).toString();
      } else {
        result = column.formatter(row, row[column.prop]);
      }
    }
  } else if (Array.isArray(result)) {
    result = result.join(separator || DIC_SHOW_SPLIT);
  }
  return result;
};

/**
 * 根据位数获取*密码程度
 */
export const getPasswordChar = (result = '', char) => {
  let len = result.toString().length;
  result = '';
  for (let i = 0; i < len; i++) {
    result = result + char;
  }
  return result;
};

/**
 * 根据字典的value显示label
 */

export const getDicValue = (list, value, props = {}) => {
  let isArray = Array.isArray(value);
  value = isArray ? value : [value];
  let result = [];
  let labelKey = props[DIC_PROPS.label] || DIC_PROPS.label;
  let groupsKey = props[DIC_PROPS.groups] || DIC_PROPS.groups;
  let dic = deepClone(list);
  dic.forEach((ele) => {
    if (ele[groupsKey]) {
      dic = dic.concat(ele[groupsKey]);
      delete ele[groupsKey];
    }
  });
  value.forEach((ele) => {
    let obj = findNode(dic, props, ele) || {};
    result.push(obj[labelKey] || ele);
  });
  if (isArray) {
    return result;
  } else {
    return result.join('');
  }
};

export function getAsVal(obj, bind = '') {
  let result = deepClone(obj);
  if (validatenull(bind)) return result;
  bind.split('.').forEach((ele) => {
    result = !validatenull(result[ele]) ? result[ele] : '';
  });
  return result;
}

/**
 * 字符串数据类型转化
 */
export const detailDataType = (value, type) => {
  if (validatenull(value)) return value;
  if (type === 'number') {
    return Number(value);
  } else if (type === 'string') {
    return value + '';
  } else {
    return value;
  }
};

/**
 * 对象深拷贝
 */
function deepClone(data) {
  let type = getObjType(data);
  let obj;
  if (type === 'array') {
    obj = [];
  } else if (type === 'object') {
    obj = {};
  } else {
    //不再具有下一层次
    return data;
  }
  if (type === 'array') {
    for (let i = 0, len = data.length; i < len; i++) {
      obj.push(deepClone(data[i]));
    }
  } else if (type === 'object') {
    for (let key in data) {
      obj[key] = deepClone(data[key]);
    }
  }
  return obj;
}

function getObjType(obj) {
  const toString = Object.prototype.toString;
  const map = {
    '[object Boolean]': 'boolean',
    '[object Number]': 'number',
    '[object String]': 'string',
    '[object Function]': 'function',
    '[object Array]': 'array',
    '[object Date]': 'date',
    '[object RegExp]': 'regExp',
    '[object Undefined]': 'undefined',
    '[object Null]': 'null',
    '[object Object]': 'object'
  };
  if (obj instanceof Element) {
    return 'element';
  }
  return map[toString.call(obj)];
}

/**
 * 验证是否存在true/false
 */
function vaildData(val, dafult) {
  if (typeof val === 'boolean') {
    return val;
  }
  return !validatenull(val) ? val : dafult;
}

function getResData(data, props, dataType) {
  const bind = props.bind || '';
  const list = bind.split('.');
  let res;
  if (list[0] === '') {
    let result = data.data;
    if (result) {
      res = Array.isArray(result) ? result : [result];
    } else {
      res = data;
    }
  }
  res = getAsVal(res, bind);
  if (dataType) res = getDataType(res, props, dataType);
  return res;
}

function getDataType(list = [], props = {}, type) {
  let valueKey = props.value || DIC_PROPS.value;
  let childrenKey = props.children || DIC_PROPS.children;
  list.forEach((ele) => {
    ele[valueKey] = detailDataType(ele[valueKey], type);
    if (ele[childrenKey]) getDataType(ele[childrenKey], props, type);
  });
  return list;
}
